/* $Id: RepoReader.java 89 2008-01-22 02:38:52Z osborn.steven $
 * 
 * Undroid Package Manager
 *
 * Copyright 2007 Steven Osborn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */
package com.bitsetters.undroid.helpers;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;


import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.Context;
import android.util.Log;

import com.bitsetters.undroid.AppEntry;
import com.bitsetters.undroid.exception.RepoException;

/**
 * 
 * @author Steven Osborn - http://steven.bitsetters.com
 */
public class RepoReader {

    public static final String TAG = "UNDROID[RepoReader]";
    public static final String UNDROID_REPO = "http://undroid.net/repo/";
    public static final long SYNC_INTERVAL = 10800000; // milliseconds
    public Context mContext;
    public String mChecksum;
    public int mRepoId;
    private DbHelper mDb;

    /**
     * Constructor
     * 
     * @param ctx
     */
    public RepoReader(Context ctx,DbHelper db) {
        mContext = ctx;
        mDb = db;
    }
    
    /**
     * Fetches checksum.txt and stores it as a member variable, throws 
     * exceptions if it fails to fetch the file or if the file is empty.
     * 
     * @param location
     * @return
     * @throws IOException
     * @throws RepoException 
     */
    public static String getRepoChecksum(String location) throws IOException,
        RepoException {
        
        String checksum_url = location.concat("checksum.txt");
        URL u = new URL(checksum_url);
        BufferedReader InFile = new BufferedReader( 
                new InputStreamReader(u.openStream()) );
        String sum = InFile.readLine().split(" ")[0].trim();
        InFile.close();
        
        if(sum.length() == 0) {
            throw new RepoException("Fetched 0 length checkusm");
        }
        
        return sum;
    }

    /**
     * This is the function that makes all of the magic happen.
     * 
     * @param repoLocation
     * @throws NoSuchAlgorithmException
     * @throws RepoException
     * @throws IOException
     * @throws XmlPullParserException
     */
    public void getRepo(String repoLocation) throws NoSuchAlgorithmException,
        RepoException, IOException, XmlPullParserException {
        mChecksum = getRepoChecksum(repoLocation);
        
        // If we synced this repo recently skip it
        long now = System.currentTimeMillis();
        long last_time = mDb.getRepoTimestamp(repoLocation);
        if(now < last_time + SYNC_INTERVAL) {
            //Log.d(TAG,"Skipping Repo Sync, it's not time yet.");
            //return;
        }
        
        // Fetch the checksum.txt and see if packagelist has changed
        // since last time we fetched it. If it hasn't changed, just skip it.
        String lastSum = mDb.getLastChecksum(repoLocation);
        if(lastSum != null && mChecksum.compareTo(lastSum) == 0) {
            
            // Since nothing has changed since last time, go ahead
            // and bump our timestamp
            mDb.updateRepoTimeStamp(repoLocation);
            Log.d(TAG,"Skipping Repo Sync, it hasn't changed since last time.");
            return;
        }
        mRepoId =  mDb.getRepoId(repoLocation);
        
        // Updating checksum also touches the time stamp
        mDb.updateRepoCheckSum(repoLocation, mChecksum);
        
        // Fetch the repository XML
        getRepoXML(repoLocation);
        
        // Remove all program entries from this repository before continuing 
        mDb.resetRepo(repoLocation);
        
        // Parse the XML data into SQLite
        processRaw();
    }
    
    /**
     * Parse packagelist.raw into our SQLite database
     * 
     * @throws XmlPullParserException
     * @throws IOException
     */
    public void processRaw() throws XmlPullParserException,
        IOException {
        
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        XmlPullParser xpp = factory.newPullParser();
            
        InputStream in = mContext.openFileInput("packagelist.raw");
        xpp.setInput(in, "ISO-8859-1");

        int eventType = xpp.getEventType();
        String currentText = "";

        AppEntry pkg = new AppEntry();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            if(eventType == XmlPullParser.END_TAG) {
                if(xpp.getName().compareTo("package") == 0) {
                    pkg.repoId = mRepoId;
                    mDb.addApp(pkg);
                    Log.d(TAG,pkg.toString());
                    pkg = new AppEntry();
                } else if(xpp.getName().compareTo("name") == 0) {
                    pkg.author = currentText.trim();
                } else if(xpp.getName().compareTo("label") == 0) {                        
                    pkg.label = currentText.trim();
                } else if(xpp.getName().compareTo("package_name") == 0) {                        
                    pkg.package_name = currentText.trim();
                } else if(xpp.getName().compareTo("category") == 0) {
                    pkg.categories.add(Integer.valueOf(currentText.trim()));
                } else if(xpp.getName().compareTo("version") == 0) {
                    pkg.version = currentText.trim();
                } else if(xpp.getName().compareTo("release") == 0) {
                    pkg.release = Integer.valueOf(currentText.trim());
                } else if(xpp.getName().compareTo("location") == 0) {
                    pkg.location = currentText.trim();
                } else if(xpp.getName().compareTo("size") == 0) {
                   pkg.size = Integer.valueOf(currentText.trim());
                } else if(xpp.getName().compareTo("author") == 0) {
                    //skip
                } else if(xpp.getName().compareTo("website") == 0) {
                    pkg.website = currentText.trim();
                } else if(xpp.getName().compareTo("summary") == 0) {
                    pkg.summary = currentText.trim();
                } else if(xpp.getName().compareTo("description") == 0) {
                    pkg.description = currentText.trim();
                } else if(xpp.getName().compareTo("license") == 0) {
                    pkg.license = currentText.trim();
                } else if(xpp.getName().compareTo("copyright") == 0) {
                    pkg.copyright = currentText.trim();
                } else if(xpp.getName().compareTo("checksum") == 0) {
                    pkg.checksum = currentText.trim(); 
                }
                currentText = "";
            } else if(eventType == XmlPullParser.TEXT) {
                currentText += xpp.getText();
            }
            if(xpp.getName() != null && xpp.getName().trim().length() > 0) {
                //Log.d(TAG,"XPP: " + xpp.getName());
            }
            eventType = xpp.next();
        }
    }
     
    /**
     * Read packagelist.xml from a repository and save it to packagelist.raw
     * and check to ensure that the md5 sum of packagelist.raw matches the
     * one we got from checksum.txt, if it doesn't throw RepoException
     * 
     * @param location
     * @throws RepoException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public void getRepoXML(String location) throws RepoException, IOException,
        NoSuchAlgorithmException {
        
        String repo_url = location.concat("packagelist.xml");
        FileOutputStream out;
        out = mContext.openFileOutput("packagelist.raw",0);
        URL u = new URL(repo_url);
        
        Security.addProvider(new BouncyCastleProvider());
        MessageDigest digest = MessageDigest.getInstance("MD5");
        
        BufferedReader in = new BufferedReader(
                new InputStreamReader(u.openStream()) );
        
        int buf = 0; 
        while ((buf = in.read()) >= 0) {
            out.write((char) buf);
            digest.update((byte) buf);
        }

        byte[] md5sum = digest.digest();
        BigInteger bigInt = new BigInteger(1, md5sum);
        String filehash = bigInt.toString(16);

        if(mChecksum.compareTo(filehash) != 0) {
            throw new RepoException("Checksum does not match packagelist.xml");
        }

        in.close();
        out.flush();
        out.close();
        
        return;
    }
    
    
}
